package server.connector.http;

import server.connector.ResponseStream;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/*
封装输出流,当需要分块发送时,按分块发送规则写
否则至多写length个字节,当length < 0,无限制
当流已将关闭时,再关闭会抛出IOException异常
 */
/*
2023/1/30 该类内容全部写完,后续直接使用,无需修改
by cyl
 */
public class HttpResponseStream extends ResponseStream {
    //分块发送
    private boolean useChunking = false;
    //是否可以写内容
    private boolean writeContent = false;
    //是否块大小已经发送,只需要写内容
    private boolean writingChunk;
    public HttpResponseStream(HttpResponseImpl response) {
        super(response);
        checkChunking(response);
        checkHead(response);
    }

    //检查是否允许分块发送,结果保存在useChunking中
    void checkChunking(HttpResponseImpl response) {
        //如果之前写过数据,不能改变发送模式
        if (count != 0)
            return;
        //当响应行,响应头未发送且未指定content-length且响应状态不是304时,可以使用分块发送
        useChunking = !response.isCommitted() &&
                response.getContentLength() == -1 &&
                response.getStatus() != HttpServletResponse.SC_NOT_MODIFIED;
        //应该分块,但响应禁止分块,发送完后应该关闭连接
        if (!response.isAllowChunking() && useChunking) {
            response.setHeader("Connection", "close");
        }
        //当应该分块且响应发送后不断开连接时,使用分块
        useChunking = useChunking && !response.isCloseConnection();
        if (useChunking) {
            response.setHeader("Transfer-Encoding", "chunked");
        } else if (response.isAllowChunking()) {
            response.removeHeader("Transfer-Encoding", "chunked");
        }
    }

    protected void checkHead(HttpResponseImpl response) {
        HttpRequestImpl request = response.getRequest();
        //HEAD方法跟GET方法相同,只不过服务器响应时不会返回消息体。
        if ("HEAD".equals(request.getMethod())) {
            writeContent = false;
        } else {
            writeContent = true;
        }
    }


    @Override
    public void write(int b) throws IOException {
        if (suspended)
            return;
        if (!writeContent)
            return;
        if (useChunking && !writingChunk) {
            writingChunk = true; //print方法是ServletOutputStream的方法,该方法又会调回write方法,所以要用writingChunk变量,避免造成栈溢出
            try {
                print("1\r\n");
                super.write(b);
                println();
            } finally {
                writingChunk = false;
            }

        } else {
            super.write(b);
        }
    }

    @Override
    public void write(byte[] b, int off, int len) throws IOException {
        if (suspended)
            return;
        if (!writeContent)
            return;
        if (useChunking && !writingChunk) {
            if (len > 0) {
                writingChunk = true;
                try {
                    println(Integer.toHexString(len));
                    super.write(b, off, len);
                    println();
                } finally {
                    writingChunk = false;
                }

            }
        } else {
            super.write(b, off, len);
        }
    }

    @Override
    public void close() throws IOException {
        if (suspended)
            throw new IOException("流已被关闭");
        if (!writeContent)
            return;
        if (useChunking) {
            writingChunk = true;
            try {
                print("0\r\n\r\n");
            } finally {
                writingChunk = false;
            }

        }
        super.close();
    }
}
